﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Input;
using CDC.SampleShell.Domain;

namespace CDC.SampleShell.Applications
{
    public sealed class ProjectViewModel : ViewModelBase, IDisposable
    {
        #region Members
        private string _viewShowing = "home";
        private bool _isProcessingRecords = false;
        private bool _isBusy = false;
        private string _loadingStatus = String.Empty;
        private readonly string _primaryFormName = String.Empty;
        private ImportViewModel _importViewModel = null;
        private ExportViewModel _exportViewModel = null;
        private ReportsViewModel _reportsViewModel = null;
        #endregion // Members

        #region Properties
        public bool IsProcessingRecords
        {
            get { return _isProcessingRecords; }
            private set { _isProcessingRecords = value; RaisePropertyChanged("IsProcessingRecords"); }
        }

        public bool IsBusy
        {
            get { return _isBusy; }
            private set { _isBusy = value; RaisePropertyChanged("IsBusy"); }
        }

        public string LoadingStatus
        {
            get { return _loadingStatus; } 
            set { _loadingStatus = value; RaisePropertyChanged("LoadingStatus"); }
        }

        public ImportViewModel ImportViewModel 
        {
            get { return _importViewModel; }
            set { _importViewModel = value; RaisePropertyChanged("ImportViewModel"); } 
        }

        public ExportViewModel ExportViewModel
        {
            get { return _exportViewModel; }
            set { _exportViewModel = value; RaisePropertyChanged("ExportViewModel"); }
        }

        public ReportsViewModel ReportsViewModel
        {
            get { return _reportsViewModel; }
            set { _reportsViewModel = value; RaisePropertyChanged("ReportsViewModel"); }
        } 

        public string ViewShowing
        {
            get
            {
                return _viewShowing;
            }
            set
            {
                if (_viewShowing != value)
                {
                    _viewShowing = value;
                    RaisePropertyChanged("ViewShowing");
                }
            }
        }
        private Project Project { get; set; }

        public ObservableCollection<RecordViewModel> RecordCollection { get; private set; }
        public ICollectionView RecordCollectionView { get; private set; }
        public ObservableCollection<FormViewModel> FormCollection { get; private set; }

        public ICollectionView FormCollectionView { get; private set; }

        #endregion // Properties

        #region Constructors
        public ProjectViewModel(string filePath, string primaryFormName)
            : base()
        {
            Contract.Requires(!String.IsNullOrEmpty(filePath));
            Contract.Requires(!String.IsNullOrEmpty(primaryFormName));

            Contract.Ensures(Project != null);
            Contract.Ensures(!String.IsNullOrEmpty(_primaryFormName));
            Contract.Ensures(ImportViewModel != null);
            Contract.Ensures(ExportViewModel != null);
            Contract.Ensures(ReportsViewModel != null);
            Contract.Ensures(Project.Views.Contains(primaryFormName));

            Project = new Project(filePath);
            Project.RecordInsertedFromServer += Project_RecordInsertedFromServer;
            Project.RecordUpdatedFromServer += Project_RecordUpdatedFromServer;

            _primaryFormName = primaryFormName;

            ImportViewModel = new Applications.ImportViewModel(Project, Project.Views[_primaryFormName]);
            ImportViewModel.ImportStarted += BusyStart;
            ImportViewModel.ImportStopped += BusyStop;

            ExportViewModel = new Applications.ExportViewModel(Project, Project.Views[_primaryFormName]);
            ExportViewModel.ExportStarted += BusyStart;
            ExportViewModel.ExportStopped += BusyStop;

            ReportsViewModel = new Applications.ReportsViewModel(Project);

            RecordCollection = new ObservableCollection<RecordViewModel>();
            FormCollection = new ObservableCollection<FormViewModel>();

            FormCollectionView = new CollectionViewSource { Source = FormCollection }.View;

            RepopulateRecordCollection();

            foreach (Epi.View view in Project.Views)
            {
                FormViewModel formViewModel = new FormViewModel(view, this);
                FormCollection.Add(formViewModel);
            }

            RecordCollectionView = new ListCollectionView(RecordCollection);
        }
        #endregion // Constructors

        #region Event Handlers
        private void Project_RecordUpdatedFromServer(object sender, Domain.Events.RecordUpdatedFromServerArgs e)
        {
            foreach (var vm in RecordCollection)
            {
                if (vm.GlobalRecordId.Equals(e.Record.GlobalRecordId))
                {
                    vm.NotifyOnAllProperties();
                }
            }
        }

        private void Project_RecordInsertedFromServer(object sender, Domain.Events.RecordInsertedFromServerArgs e)
        {
            RecordViewModel newRecord = new RecordViewModel(e.Record);
            RecordCollection.Add(newRecord);
        }
        #endregion // Event Handlers

        #region Methods

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (Project != null)
                {
                    Project.RecordInsertedFromServer -= Project_RecordInsertedFromServer;
                    Project.RecordUpdatedFromServer -= Project_RecordUpdatedFromServer;

                    if (ImportViewModel != null)
                    {
                        ImportViewModel.ImportStarted -= BusyStart;
                        ImportViewModel.ImportStopped -= BusyStop;
                    }

                    if (ExportViewModel != null)
                    {
                        ExportViewModel.ExportStarted -= BusyStart;
                        ExportViewModel.ExportStopped -= BusyStop;
                    }
                }
            }
            // free native resources if there are any.
        }

        private void RepopulateRecordCollection()
        {
            IsProcessingRecords = true;
            LoadingStatus = "Getting records from database (step 1 of 3)...";

            Task.Factory.StartNew(
                () =>
                {
                    Project.RepopulateRecordCollection();
                },
                 System.Threading.CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).ContinueWith(
                 delegate
                 {
                     LoadingStatus = "Clearing memory (2 of 3)...";

                     foreach (RecordViewModel recordViewModel in RecordCollection)
                     {
                         recordViewModel.RecordSaved -= recordViewModel_RecordSaved;
                         recordViewModel.RecordDeletionRequested -= recordViewModel_RecordDeletionRequested;
                     }

                     RecordCollection.Clear();

                     LoadingStatus = "Building data structures (3 of 3)...";

                     foreach (Record record in Project.GetRecords(Project.Views[_primaryFormName]))
                     {
                         RecordViewModel recordViewModel = new RecordViewModel(record);
                         RecordCollection.Add(recordViewModel);

                         recordViewModel.RecordSaved += recordViewModel_RecordSaved;
                         recordViewModel.RecordDeletionRequested += recordViewModel_RecordDeletionRequested;
                     }

                     IsProcessingRecords = false;
                     LoadingStatus = String.Empty;

                 }, TaskScheduler.FromCurrentSynchronizationContext());
        }

        private void SwitchViewExecute(string view)
        {
            if (!IsBusy)
            {
                ViewShowing = view;
            }
        }

        private void AddRecordExecute()
        {
            RecordViewModel.AddRecord(Project, Project.Views[_primaryFormName]);
            RepopulateRecordCollection();
        }

        private void OpenFormInDesignerExecute()
        {
            // Opens Form Designer with the specified project. The only requirements here are ensuring the command-line parameters are correct.

            System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
            string commandText = System.IO.Path.GetDirectoryName(a.Location) + "\\MakeView.exe";

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.FileName = commandText;
            proc.StartInfo.Arguments = " /project:\"" + Project.FilePath + "\" /view:\"" + (FormCollectionView.CurrentItem as FormViewModel).Name;
            proc.StartInfo.UseShellExecute = true;
            proc.Start();
        }

        private void DeleteRecordsExecute()
        {
            IsProcessingRecords = true;

            List<RecordViewModel> recordsToDelete = new List<RecordViewModel>();

            foreach (RecordViewModel rvm in RecordCollection)
            {
                if (rvm.IsSelectedForDeletion)
                {
                    if (rvm.DeleteRecordCommand.CanExecute(null))
                    {
                        recordsToDelete.Add(rvm);
                    }
                }
            }

            foreach (RecordViewModel rvm in recordsToDelete)
            {
                rvm.DeleteRecordCommand.Execute(null);
            }

            IsProcessingRecords = false;
        }


        private void OpenFormInEnterExecute()
        {
            // Opens Enter with the specified project. The only requirements here are ensuring the command-line parameters are correct. Unlike
            // the form designer command, however, we can optionally specify a /record: parameter (which isn't done in this example) to open
            // Enter on a given record. * opens on a new record. The default is to open Enter on the first record, which may not be the
            // desired behavior for applications, although it is the default behavior for Epi Info 7.

            System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
            string commandText = System.IO.Path.GetDirectoryName(a.Location) + "\\Enter.exe";

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.FileName = commandText;
            proc.StartInfo.Arguments = " /project:\"" + Project.FilePath + "\" /view:\"" + (FormCollectionView.CurrentItem as FormViewModel).Name;
            proc.StartInfo.UseShellExecute = true;
            proc.Start();
        }

        private bool CanExecuteDeleteRecordsCommand()
        {
            if (IsProcessingRecords || IsBusy) return false;

            foreach (RecordViewModel rvm in RecordCollection)
            {
                if (rvm.IsSelectedForDeletion) return true;
            }
            return false;
        }

        private bool CanExecuteOpenFormInDesigner()
        {
            if (FormCollectionView == null || FormCollectionView.CurrentItem == null) return false;
            return true;
        }

        private bool CanExecuteRepopulateCollections()
        {
            if (IsProcessingRecords || IsBusy) return false;
            return true;
        }

        private void LaunchDashboardCanvasExecute(string dashboard)
        {
            // Launches the dashboard with a given file name. Note the XmlDocument references. What's happening here
            // is that we're opening the canvas file, stripping out the file path, and replacing with the file path
            // to our executing assembly. This is needed because Epi Info 7 canvas files use absolute paths to the
            // location of the database they connect to, which makes them impossible to use across databases. The
            // code added here works around that limitation. At the end, we call shell execute to run the Dashboard.
            // There is no API call to load the Dashboard from within the shell.

            string fileName = dashboard + ".cvs7";

            System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
            string commandText = System.IO.Path.GetDirectoryName(a.Location) + "\\AnalysisDashboard.exe";

            var doc = new System.Xml.XmlDocument();
            doc.XmlResolver = null;
            doc.Load(System.IO.Path.GetDirectoryName(a.Location) + "\\Projects\\SampleShell\\" + fileName);
            System.Xml.XmlNode node = doc.SelectSingleNode("DashboardCanvas/dashboardHelper/projectPath");
            node.InnerText = System.IO.Path.GetDirectoryName(a.Location) + "\\Projects\\SampleShell\\" + Project.Name + ".prj";
            doc.Save(System.IO.Path.GetDirectoryName(a.Location) + "\\Projects\\SampleShell\\" + fileName);

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.FileName = commandText;
            proc.StartInfo.Arguments = string.Format("\"{0}\"", "Projects\\SampleShell\\" + fileName);
            proc.StartInfo.UseShellExecute = true;
            proc.Start();
        }

        private void LaunchDashboardFreeFormExecute()
        {
            // Same as the above method, except we're not running the Dashboard with a canvas file.

            System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
            string commandText = System.IO.Path.GetDirectoryName(a.Location) + "\\AnalysisDashboard.exe";

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.FileName = commandText;
            proc.StartInfo.UseShellExecute = true;
            proc.Start();
        }

        private void SearchRecords(string searchString)
        {
            if (String.IsNullOrEmpty(searchString))
            {
                RecordCollectionView.Filter = null;
            }
            else
            {
                RecordCollectionView.Filter = new Predicate<object>
                (
                    r =>
                    {
                        RecordViewModel rvm = r as RecordViewModel;
                        return rvm.ContainsSearchString(searchString);
                    }
                );
            }
        }
        #endregion // Methods

        #region Event Handlers
        private void recordViewModel_RecordDeletionRequested(object sender, EventArgs e)
        {
            RecordViewModel recordViewModel = sender as RecordViewModel;
            if (recordViewModel != null)
            {
                Project.DeleteRecord(recordViewModel.GlobalRecordId);

                recordViewModel.RecordSaved -= recordViewModel_RecordSaved;
                recordViewModel.RecordDeletionRequested -= recordViewModel_RecordDeletionRequested;

                RecordCollection.Remove(recordViewModel);
            }
        }

        private void recordViewModel_RecordSaved(object sender, Domain.Events.RecordSavedEventArgs e)
        {
            Project.RefreshRecord(e.GlobalRecordId);
        }

        void BusyStop(object sender, EventArgs e)
        {
            IsBusy = false;
        }

        void BusyStart(object sender, EventArgs e)
        {
            IsBusy = true;
        }
        #endregion // Event Handlers

        #region Commands
        public ICommand RepopulateCollectionsCommand { get { return new RelayCommand(RepopulateRecordCollection, CanExecuteRepopulateCollections); } }
        public ICommand SwitchViewCommand { get { return new RelayCommand<string>(SwitchViewExecute); } }
        public ICommand AddRecordCommand { get { return new RelayCommand(AddRecordExecute); } }
        public ICommand OpenFormInDesignerCommand { get { return new RelayCommand(OpenFormInDesignerExecute, CanExecuteOpenFormInDesigner); } }
        public ICommand DeleteRecordsCommand { get { return new RelayCommand(DeleteRecordsExecute, CanExecuteDeleteRecordsCommand); } }
        public ICommand OpenFormInEnterCommand { get { return new RelayCommand(OpenFormInEnterExecute, CanExecuteOpenFormInDesigner); } }
        public ICommand LaunchDashboardCanvasCommand { get { return new RelayCommand<string>(LaunchDashboardCanvasExecute); } }
        public ICommand LaunchDashboardFreeFormCommand { get { return new RelayCommand(LaunchDashboardFreeFormExecute); } }
        public ICommand SearchRecordsCommand { get { return new RelayCommand<string>(SearchRecords); } }
        #endregion // Commands
    }
}
